
#include "xml_tree.hpp"
#include "xml_attrcast.hpp"

//#include <type_traits>

namespace mp
{
	namespace xml
	{
		template <typename T>
		struct name_trait
		{
			static std::string name()
			{
				return "<unknown>";
			}
			// missing name_trait for type ...
			// if you get here you are probably using a new type
			// in the XML file. Just add a name trait for the new
			// type below.
			//static_assert( sizeof(T) == 0, "missing name_trait for the type");
		};

#define DEFINE_NAME_TRAIT( type, type_name )                            \
	template <>                                                         \
		struct name_trait<type>                                             \
		{                                                                   \
		static std::string name() { return std::string("type ") + type_name; } \
		};


		DEFINE_NAME_TRAIT( double, "double")
			DEFINE_NAME_TRAIT( float, "float")
			DEFINE_NAME_TRAIT( unsigned, "unsigned")
			DEFINE_NAME_TRAIT( bool, "bool")
			DEFINE_NAME_TRAIT( int, "int" )
			DEFINE_NAME_TRAIT( std::string, "string" )
			DEFINE_NAME_TRAIT( xml_color, "color" )

			xml_tree::xml_tree(std::string const& encoding)
			: node_(*this, std::string("<root>")),
			file_()
		{
			node_.set_processed(true); //root node is always processed
		}

		void xml_tree::set_filename(std::string const& fn)
		{
			file_ = fn;
		}

		std::string const& xml_tree::filename() const
		{
			return file_;
		}

		xml_node &xml_tree::root()
		{
			return node_;
		}

		const xml_node &xml_tree::root() const
		{
			return node_;
		}

		xml_attribute::xml_attribute(const char * value_)
			: value(value_),
			processed(false)
		{

		}

		node_not_found::node_not_found(std::string const& node_name)
			: node_name_(node_name) {}

		const char* node_not_found::what() const throw()
		{
			return ("Node "+node_name_+ "not found").c_str();
		}

		node_not_found::~node_not_found() throw() {}


		attribute_not_found::attribute_not_found(
			std::string const& node_name,
			std::string const& attribute_name)
			:
		node_name_(node_name),
			attribute_name_(attribute_name) {}

		const char* attribute_not_found::what() const throw()
		{
			return ("Attribute '" + attribute_name_ +"' not found in node '"+node_name_+ "'").c_str();
		}

		attribute_not_found::~attribute_not_found() throw() {}

		more_than_one_child::more_than_one_child(std::string const& node_name)
			: node_name_(node_name) {}

		const char* more_than_one_child::what() const throw()
		{
			return ("More than one child node in node '" + node_name_ +"'").c_str();
		}

		more_than_one_child::~more_than_one_child() throw() {}

		xml_node::xml_node(xml_tree &tree, const std::string& name, unsigned line, bool is_text)
			: tree_(tree),
			name_(name),
			is_text_(is_text),
			line_(line),
			processed_(false),
			ignore_(false) {}

		std::string xml_node::xml_text = "<xmltext>";

		std::string const& xml_node::name() const
		{
			if (!is_text_)
			{
				return name_;
			}
			else
			{
				return xml_text;
			}
		}

		std::string const& xml_node::text() const
		{
			if (is_text_)
			{
				processed_ = true;
				return name_;
			}
			else
			{
				//throw config_error("text() called on non-text node", *this);
			}
		}

		std::string const& xml_node::filename() const
		{
			return tree_.filename();
		}

		bool xml_node::is_text() const
		{
			return is_text_;
		}

		bool xml_node::is(std::string const& name) const
		{
			if (name_ == name)
			{
				processed_ = true;
				return true;
			}
			return false;
		}

		xml_node &xml_node::add_child(const std::string& name, unsigned line, bool is_text)
		{
			children_.push_back(xml_node(tree_, name, line, is_text));
			return children_.back();
		}

		void xml_node::add_attribute(const char * name, const char * value)
		{
			//attributes_.emplace(name,xml_attribute(value));
			attributes_[name] = xml_attribute(value);
		}

		xml_node::attribute_map const& xml_node::get_attributes() const
		{
			return attributes_;
		}

		void xml_node::set_processed(bool processed) const
		{
			processed_ = processed;
		}

		bool xml_node::processed() const
		{
			return processed_;
		}

		void xml_node::set_ignore(bool ignore) const
		{
			ignore_ = ignore;
		}

		bool xml_node::ignore() const
		{
			return ignore_;
		}

		std::size_t xml_node::size() const
		{
			return children_.size();
		}

		xml_node::const_iterator xml_node::begin() const
		{
			return children_.begin();
		}

		xml_node::const_iterator xml_node::end() const
		{
			return children_.end();
		}

		xml_node & xml_node::get_child(std::string const& name)
		{
			std::list<xml_node>::iterator itr = children_.begin();
			std::list<xml_node>::iterator end = children_.end();
			for (; itr != end; ++itr)
			{
				if (!(itr->is_text_) && itr->name_ == name)
				{
					itr->set_processed(true);
					return *itr;
				}
			}
			throw node_not_found(name);
		}

		xml_node const& xml_node::get_child(std::string const& name) const
		{
			xml_node const* node = get_opt_child(name);
			if (!node) throw node_not_found(name);
			return *node;
		}

		xml_node const* xml_node::get_opt_child(std::string const& name) const
		{
			const_iterator itr = children_.begin();
			const_iterator end = children_.end();
			for (; itr != end; itr++)
			{
				if (!(itr->is_text_) && itr->name_ == name)
				{
					itr->set_processed(true);
					return &(*itr);
				}
			}
			return 0;
		}

		bool xml_node::has_child(std::string const& name) const
		{
			return get_opt_child(name) != 0;
		}

		bool xml_node::has_attribute(std::string const& name) const
		{
			return attributes_.count(name) == 1 ? true : false;
		}

		template <typename T>
		optional<T> xml_node::get_opt_attr(std::string const& name) const
		{
			if (attributes_.empty()) return optional<T>();
			std::map<std::string, xml_attribute>::const_iterator itr = attributes_.find(name);
			if (itr ==  attributes_.end()) return optional<T>();
			itr->second.processed = true;

			T result;
			bool ok = xml_attribute_cast<T>(tree_, std::string(itr->second.value),result);
			optional<T> r; r.mutable_value() = result;
			return r;
		}

		template <typename T>
		T xml_node::get_attr(std::string const& name, T const& default_opt_value) const
		{
			optional<T> v = get_opt_attr<T>(name);
			if (v.isSet()) return *v;
			return default_opt_value;
		}

		template <typename T>
		T xml_node::get_attr(std::string const& name) const
		{
			optional<T> v = get_opt_attr<T>(name);
			if (v.isSet()) return *v;
			throw attribute_not_found(name_, name);
		}

		std::string const& xml_node::get_text() const
		{
			// FIXME : return boost::optional<std::string const&>
			if (children_.empty())
			{
				if (is_text_)
				{
					return name_;
				}
				else
				{
					const static std::string empty;
					return empty;
				}
			}
			if (children_.size() == 1)
			{
				return children_.front().text();
			}
			throw more_than_one_child(name_);
		}


		template <typename T>
		T xml_node::get_value() const
		{
			T result;
			bool ok = xml_attribute_cast<T>(tree_, get_text(),result);
			if (!ok)
			{
				//throw config_error(std::string("Failed to parse value. Expected ")
				//                   + name_trait<T>::name() +
				//                   " but got '" + get_text() + "'", *this);
			}
			return result;
		}

		unsigned xml_node::line() const
		{
			return line_;
		}

		std::string xml_node::line_to_string() const
		{
			std::string number;
			to_string(number,line_);
			return number;
		}


#define compile_get_opt_attr(T) template optional<T> xml_node::get_opt_attr<T>(std::string const&) const
#define compile_get_attr(T) template T xml_node::get_attr<T>(std::string const&) const; template T xml_node::get_attr<T>(std::string const&, T const&) const
#define compile_get_value(T) template T xml_node::get_value<T>() const

		compile_get_opt_attr(bool);
		compile_get_opt_attr(std::string);
		compile_get_opt_attr(unsigned);
		compile_get_opt_attr(int);
		compile_get_opt_attr(float);
		compile_get_opt_attr(double);
		compile_get_opt_attr(xml_color);

		compile_get_attr(bool);
		compile_get_attr(std::string);
		compile_get_attr(unsigned);
		compile_get_attr(int);
		compile_get_attr(float);
		compile_get_attr(double);
		compile_get_attr(xml_color);

		compile_get_value(bool);
		compile_get_value(std::string);
		compile_get_value(unsigned);
		compile_get_value(int);
		compile_get_value(float);
		compile_get_value(double);
		compile_get_value(xml_color);

	}
}